#!/usr/bin/env python3
"""
Compile a historical TODO archive by harvesting entries from todos-info.md
patch logs and the current todos.md file.

The script extracts every TODO bullet that was added via `apply_patch`
operations recorded in todos-info.md, keeps the latest status for duplicate
items, attaches any accompanying summary lines, and merges in the current
state of todos.md. The resulting consolidated list is written to the desired
output path (default: todo-all.md).
"""

from __future__ import annotations

import argparse
import re
from collections import OrderedDict
from pathlib import Path
from typing import List


BULLET_PATTERN = re.compile(r"^- \[[ xX]\]\s*(.*)")


def normalize_display(text: str) -> str:
    """Correct mojibake sequences produced by double-encoded UTF-8."""
    cleaned = text
    for _ in range(3):
        try:
            candidate = cleaned.encode("latin1").decode("utf-8")
        except (UnicodeEncodeError, UnicodeDecodeError):
            break
        if candidate == cleaned:
            break
        cleaned = candidate
    return cleaned


def _decode_patch(block: str) -> str:
    """Convert the escaped patch text into its literal form."""
    decoded = block
    for _ in range(5):
        if "\\n" not in decoded and '\\"' not in decoded and "\\\\" not in decoded:
            break
        decoded = bytes(decoded, "utf-8").decode("unicode_escape")
    return decoded


def normalize_key(bullet_line: str) -> str:
    """Return a canonical key for a TODO bullet (without status checkbox)."""
    match = BULLET_PATTERN.match(bullet_line)
    if not match:
        return ""
    text = match.group(1)
    return re.sub(r"\s+", " ", text).strip()


def add_entry(
    archive: "OrderedDict[str, dict]",
    bullet_line: str,
    summary_lines: List[str],
) -> None:
    """Add or update a TODO entry in the archive."""
    key = normalize_key(bullet_line.strip())
    if not key:
        return

    entry = archive.get(key)
    if entry is None:
        archive[key] = {
            "bullet": normalize_display(bullet_line.strip()),
            "summaries": [],
        }
        entry = archive[key]
    else:
        entry["bullet"] = normalize_display(bullet_line.strip())

    for summary in summary_lines:
        summary_clean = normalize_display(summary.rstrip())
        if summary_clean and summary_clean not in entry["summaries"]:
            entry["summaries"].append(summary_clean)


def harvest_from_patch_logs(
    info_text: str,
    archive: "OrderedDict[str, dict]",
) -> None:
    """Populate archive entries from todos-info.md content."""
    marker_begin = "*** Begin Patch"
    marker_update = "*** Update File: todos.md\n"
    marker_end = "*** End Patch"

    search_idx = 0
    while True:
        begin_idx = info_text.find(marker_begin, search_idx)
        if begin_idx == -1:
            break

        end_idx = info_text.find(marker_end, begin_idx)
        if end_idx == -1:
            break

        raw_patch = info_text[begin_idx : end_idx + len(marker_end)]
        patch_text = _decode_patch(raw_patch)

        update_idx = patch_text.find(marker_update)
        if update_idx == -1:
            search_idx = end_idx + len(marker_end)
            continue

        update_idx += len(marker_update)
        next_marker_idx = patch_text.find("*** ", update_idx)
        if next_marker_idx == -1:
            segment = patch_text[update_idx:]
        else:
            segment = patch_text[update_idx:next_marker_idx]

        current_key = None
        for raw_line in segment.splitlines():
            if not raw_line.startswith("+"):
                current_key = None
                continue

            content = raw_line[1:]
            if content.startswith("- ["):
                bullet = normalize_display(content.strip())
                add_entry(archive, bullet, [])
                current_key = normalize_key(bullet)
            elif content.startswith("  ") and current_key:
                entry = archive.get(current_key)
                if entry is None:
                    continue
                summary_line = normalize_display(content.rstrip())
                if summary_line and summary_line not in entry["summaries"]:
                    entry["summaries"].append(summary_line)
            else:
                current_key = None

        search_idx = end_idx + len(marker_end)


def harvest_from_current_todos(
    todos_path: Path,
    archive: "OrderedDict[str, dict]",
) -> None:
    """Merge the current todos.md state into the archive."""
    if not todos_path.exists():
        return

    bullet_line = None
    summary_lines: List[str] = []

    def flush():
        nonlocal bullet_line, summary_lines
        if bullet_line is not None:
            add_entry(archive, bullet_line, summary_lines)
        bullet_line = None
        summary_lines = []

    with todos_path.open("r", encoding="utf-8") as handle:
        for line in handle:
            stripped = line.rstrip("\n")
            if stripped.startswith("- ["):
                flush()
                bullet_line = stripped
                summary_lines = []
            elif stripped.startswith("  ") and bullet_line is not None:
                summary_lines.append(stripped)
            elif stripped.strip() == "":
                flush()
            else:
                # Non-summary text (e.g., headings) breaks the current entry.
                flush()

    flush()


def render_archive(archive: "OrderedDict[str, dict]") -> str:
    """Render the archive into Markdown format."""
    lines = ["# Historical TODO Archive", ""]
    for entry in archive.values():
        lines.append(entry["bullet"])
        for summary in entry["summaries"]:
            lines.append(summary)
        lines.append("")
    return "\n".join(lines).rstrip() + "\n"


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Compile a historical TODO archive from todos-info.md and todos.md.",
    )
    parser.add_argument(
        "--info-path",
        type=Path,
        default=Path("todos-info.md"),
        help="Path to the todos-info.md log (default: %(default)s)",
    )
    parser.add_argument(
        "--todos-path",
        type=Path,
        default=Path("todos.md"),
        help="Path to the current todos.md file (default: %(default)s)",
    )
    parser.add_argument(
        "--output-path",
        type=Path,
        default=Path("todo-all.md"),
        help="Destination path for the compiled archive (default: %(default)s)",
    )
    args = parser.parse_args()

    if not args.info_path.exists():
        raise FileNotFoundError(f"Missing todos-info log at {args.info_path}")

    info_text = args.info_path.read_text(encoding="utf-8")
    archive: "OrderedDict[str, dict]" = OrderedDict()

    harvest_from_patch_logs(info_text, archive)
    harvest_from_current_todos(args.todos_path, archive)

    output_text = render_archive(archive)
    args.output_path.write_text(output_text, encoding="utf-8")


if __name__ == "__main__":
    main()
